/********************************************************
 *
 * FILE NAME:   splitter.cpp
 *
 * DESCRIPTION:
 *  Module with signal splitter component
 *
 ********************************************************/

/********************************************************/
/*              Includes                                */
/********************************************************/
#include "splitter.h"

#include <QtQml>
#include <QDebug>

/********************************************************/
/*              Defines                                 */
/********************************************************/

/********************************************************/
/*              Macros                                  */
/********************************************************/

/********************************************************/
/*              Typedefs                                */
/********************************************************/

/********************************************************/
/*              Local Functions Declarations (LOCAL)    */
/********************************************************/

/********************************************************/
/*              Global Variables (GLOBAL/IMPORT)        */
/********************************************************/

/********************************************************/
/*              Local Module Variables (MODULE)         */
/********************************************************/

/********************************************************/
/*              Local File Variables (LOCAL)            */
/********************************************************/

/********************************************************/
/*              Functions Definitions (LOCAL/GLOBAL)    */
/********************************************************/
Splitter::Splitter(QObject *parent) : QObject(parent)
{
}

void Splitter::registerTypes(const char *uri)
{
    qmlRegisterType<Splitter>(uri, 1, 0, "Splitter");
}

bool Splitter::init(int burstLength)
{
    _burstLength = burstLength;
    _inISig = new ISignal(0, burstLength);

    _masterOSig = new MasterOSignal(*this);

    _slaveBuff = new Buffer<sample_t>(20 * burstLength);
    _slaveUnprocessed = 0;
    _slaveMtx = new QMutex();
    _slaveNotEmpty = new QWaitCondition();
    _slaveNotFull = new QWaitCondition();
    _slaveOSig = new SlaveOSignal(*this);
    return true;
}

Splitter::MasterOSignal::MasterOSignal(Splitter &context) : OSignal(0), _context(context)
{
}

bool Splitter::MasterOSignal::onBurstRequest(size_t burstLength)
{
    ISignal &inISig = *_context._inISig;

    Buffer<sample_t> &slaveBuff = *_context._slaveBuff;
    size_t &slaveUnprocessed = _context._slaveUnprocessed;
    QMutex &slaveMtx = *_context._slaveMtx;
    QWaitCondition &slaveNotEmpty = *_context._slaveNotEmpty;
    QWaitCondition &slaveNotFull = *_context._slaveNotFull;
    OSignal &slaveOSig = *_context._slaveOSig;

    inISig.requestBurst(burstLength);
    for(size_t burst = 0; burst < burstLength; ++burst)
    {
        inISig.readInSample();

        (*this)[0] = inISig[0];
        this->writeOutSample();

        // push to slave buffer
        slaveMtx.lock();

        if(slaveUnprocessed == slaveBuff.size())
        {
            slaveNotFull.wait(&slaveMtx);
        }

        slaveBuff[slaveUnprocessed] = inISig[0];
        ++slaveUnprocessed;

        if(slaveUnprocessed >= slaveOSig.burstLength())
        {
            slaveNotEmpty.wakeAll();
        }
        slaveMtx.unlock();
    }

    return true;
}

Splitter::SlaveOSignal::SlaveOSignal(Splitter &context) : OSignal(0), _context(context)
{
}

bool Splitter::SlaveOSignal::onBurstRequest(size_t burstLength)
{
    Buffer<sample_t> &buff = *_context._slaveBuff;
    size_t &buffUnprocessed = _context._slaveUnprocessed;
    QMutex &buffMtx = *_context._slaveMtx;
    QWaitCondition &buffNotEmpty = *_context._slaveNotEmpty;
    QWaitCondition &buffNotFull = *_context._slaveNotFull;

    for(size_t burst = 0; burst < burstLength; ++burst)
    {
        buffMtx.lock();
        if(buffUnprocessed == 0)
        {
            buffNotEmpty.wait(&buffMtx);
        }

        (*this)[0] = buff[0];
        this->writeOutSample();

        buff.rotate(-1);
        --buffUnprocessed;

        buffNotFull.wakeAll();
        buffMtx.unlock();
    }

    return true;
}
